<?php
class FtpUtil {
	private $cfg;
	private $conn;
	
	public function __construct($cfg = array()) {
		$this->setCfg($cfg);
		$this->connect();
	}
	
	/**
	 * close the ftp connect
	 *
	 * @return bool
	 */
	public function free() {
		return ftp_close($this->conn);
	}
	
	/**
	 * get info about the ftp server
	 * @return array : system/pwd
	 */
	public function info() {
		$info = array();
		$info['system'] = ($system = ftp_systype($this->conn)) ? $system : 'UNKNOWN';
		$info['pwd'] = ($pwd = ftp_pwd($this->conn)) ? $pwd : 'UNKNOWN';
		
		return $info;
	}
	
	/**
	 * get the file info in ftp server
	 *
	 * @param string $file
	 *
	 * @return array : mdtm/size
	 */
	public function finfo($file) {
		$info = array();
		$info['mdtm'] = (($mdtm = ftp_mdtm($this->conn, $file)) !== -1) ? $mdtm : 'UNKNOWN'; //time stamp
		$info['size'] = (($size = ftp_size($this->conn, $file)) !== -1) ? $size : 'UNKNOWN'; //bytes
		
		return $info;
	}
	
	/**
	 * change current dir
	 * @param mixed $tag:-1 means change to parent dir, string means change to the specific dir
	 * @return mixed:current dir on success, and false on fail
	 */
	public function chdir($tag = -1) {
		$ret = false;
		
		if($tag === -1 && ftp_cdup($this->conn)) {
			$ret = ftp_pwd($this->conn);
		} elseif(is_string($tag) && @ftp_chdir($this->conn, $tag)) {
			$ret = ftp_pwd($this->conn);
		}
		
		return $ret;
	}
	
	/**
	 * remove a directory(delete files in dir first)
	 *
	 * @param string $dir
	 *
	 * @return bool
	 */
	public function rmdir($dir) {
		//remove files in dir
		if($files = ftp_nlist($this->conn, $dir)) {
			foreach($files as $file) {
				//the file had the complete path
				$this->delete($file);
			}
		}
		
		return @ftp_rmdir($this->conn, $dir);
	}
	
	/**
	 * add a new dir
	 *
	 * @param string $dir
	 *
	 * @return bool
	 */
	public function mkdir($dir) {
		return ftp_mkdir($this->conn, $dir);
	}
	
	/**
	 * remove a file or dir
	 *
	 * @param string $old
	 * @param string $new
	 *
	 * @return bool
	 */
	public function rename($old, $new) {
		return @ftp_rename($this->conn, $old, $new);
	}
	
	/**
	 * delete a file
	 *
	 * @param string $file
	 *
	 * @return bool
	 */
	public function delete($file) {
		return @ftp_delete($this->conn, $file);
	}
	
	/**
	 * list files in a dir
	 *
	 * @param string $dir
	 * @param bool $raw
	 *
	 * @return mixed : array or false in fail
	 */
	public function flist($dir, $raw = false) {
		$fn = $raw ? 'ftp_rawlist' : 'ftp_nlist';
		
		return $fn($this->conn, $dir);
	}
	
	/**
	 * exec a command
	 *
	 * @param string $cmd
	 * @param string $type : raw/site/exec
	 *
	 * @return mixed
	 */
	public function exec($cmd, $type = 'raw') {
		$fn = 'ftp_' . $type;
		
		return $fn($this->conn, $cmd);
	}
	
	/**
	 * change a file's mode
	 * @param string $file
	 * @param int $mode:octal number
	 * @return bool:true on success and false on fail
	 */
	public function chmod($file, $mode = 0777) {
		return @ftp_chmod($this->conn, $mode, $file);
	}
	
	/**
	 * (continue) download a file
	 * @param string $file
	 * @param mixed $to : string
	 * @param int mode : FTP_ASCII or FTP_BINARY
	 *
	 * @return bool
	 */
	public function download($file, $to, $mode = FTP_BINARY) {		
		if(!($resumepos = @filesize($to))) {
			$resumepos = 0;
		}
		
		return ftp_get($this->conn, $to, $file, $mode, $resumepos);
	}
	
	/**
	 * (continue) download a file(nb means no blocking)
	 * @param string $file
	 * @param mixed $to : string
	 * @param int mode : FTP_ASCII or FTP_BINARY
	 *
	 * @return bool
	 */
	public function download_nb($file, $to, $mode = FTP_BINARY) {
		if(!($resumepos = @filesize($to))) {
			$resumepos = 0;
		}
		
		$ret = ftp_nb_get($this->conn, $to, $file, $mode, $resumepos);
		while ($ret == FTP_MOREDATA) {
			$ret = ftp_nb_continue($this->conn);
		}
		if ($ret != FTP_FINISHED) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * (continue) upload a file
	 *
	 * @param string $file:local file
	 * @param string $to:file in ftp server
	 * @param int $mode : FTP_BINARY OR FTP_TEXT
	 */
	public function upload($file, $to, $mode = FTP_BINARY) {
		$startpos = ftp_size($this->conn, $to);
		if($startpos === -1) $startpos = 0;
		
		if($fsize = @filesize($file)) {
			//pre alloc
			ftp_alloc($this->conn, $fsize - $startpos);
		}
		
		return ftp_put($this->conn, $to, $file, $mode, $startpos);
	}
	
	/**
	 * (continue) upload a file(nb means no blocking)
	 *
	 * @param string $file:local file
	 * @param string $to:file in ftp server
	 * @param int $mode : FTP_BINARY OR FTP_TEXT
	 */
	public function upload_nb($file, $to, $mode = FTP_BINARY) {
		$startpos = ftp_size($this->conn, $to);
		if($startpos === -1) $startpos = 0;
		
		if($fsize = @filesize($file)) {
			//pre alloc
			ftp_alloc($this->conn, $fsize - $startpos);
		}
		
		$ret = ftp_nb_put($this->conn, $to, $file, $mode, $startpos);
		while ($ret == FTP_MOREDATA) {
			$ret = ftp_nb_continue($this->conn);
		}
		if ($ret != FTP_FINISHED) {
			return false;
		} else {
			return true;
		}
	}
	
	public function __destruct() {
		$this->free();
	}
	
	/**
	 * connect to ftp server, login with specific username and pwd
	 * @return void
	 */
	private function connect() {
		$fn = $this->cfg['ssl'] ? 'ftp_ssl_connect' : 'ftp_connect';
		$this->conn = $fn($this->cfg['host'], $this->cfg['port'], $this->cfg['timeout']);
		if(!$this->conn) {
			throw new FtpException(FtpException::FTP_CONNECT_FAIL);
		}
		$status = ftp_login($this->conn, $this->cfg['username'], $this->cfg['password']);
		if(!$status) {
			throw new FtpException(FtpException::FTP_LOGIN_FAIL);
		}
		//options
		@ftp_set_option($this->conn, FTP_TIMEOUT_SEC, $this->cfg['timeout']);
		@ftp_set_option($this->conn, FTP_AUTOSEEK, true);
		//should setting when login success
		@ftp_pasv($this->conn, $cfg['pasv']);
	}
	
	/**
	 * set config
	 * @return void
	 */
	private function setCfg($cfg) {
		$this->cfg = require FRAMEWORK_FTP_ROOT . 'config.php';
		foreach($cfg as $key => $val) {
			if(array_key_exists($key, $this->cfg)) $this->cfg[$key] = $val;
		}
	}
}
?>